/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 *
 * Class: mxVmlCanvas2D
 *
 * Implements a canvas to be used for rendering VML. Here is an example of implementing a
 * fallback for SVG images which are not supported in VML-based browsers.
 *
 * (code)
 * var mxVmlCanvas2DImage = mxVmlCanvas2D.prototype.image;
 * mxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
 * {
 *   if (src.substring(src.length - 4, src.length) == '.svg')
 *   {
 *     src = 'http://www.jgraph.com/images/mxgraph.gif';
 *   }
 *
 *   mxVmlCanvas2DImage.apply(this, arguments);
 * };
 * (end)
 *
 * To disable anti-aliasing in the output, use the following code.
 *
 * (code)
 * document.createStyleSheet().cssText = mxClient.VML_PREFIX + '\\:*{antialias:false;)}';
 * (end)
 *
 * A description of the public API is available in <mxXmlCanvas2D>. Note that
 * there is a known issue in VML where gradients are painted using the outer
 * bounding box of rotated shapes, not the actual bounds of the shape. See
 * also <text> for plain text label restrictions in shapes for VML.
 */
var mxVmlCanvas2D = function (root) {
  mxAbstractCanvas2D.call(this);

  /**
   * Variable: root
   *
   * Reference to the container for the SVG content.
   */
  this.root = root;
};

/**
 * Extends mxAbstractCanvas2D
 */
mxUtils.extend(mxVmlCanvas2D, mxAbstractCanvas2D);

/**
 * Variable: path
 *
 * Holds the current DOM node.
 */
mxVmlCanvas2D.prototype.node = null;

/**
 * Variable: textEnabled
 *
 * Specifies if text output should be enabledetB. Default is true.
 */
mxVmlCanvas2D.prototype.textEnabled = true;

/**
 * Variable: moveOp
 *
 * Contains the string used for moving in paths. Default is 'm'.
 */
mxVmlCanvas2D.prototype.moveOp = "m";

/**
 * Variable: lineOp
 *
 * Contains the string used for moving in paths. Default is 'l'.
 */
mxVmlCanvas2D.prototype.lineOp = "l";

/**
 * Variable: curveOp
 *
 * Contains the string used for bezier curves. Default is 'c'.
 */
mxVmlCanvas2D.prototype.curveOp = "c";

/**
 * Variable: closeOp
 *
 * Holds the operator for closing curves. Default is 'x e'.
 */
mxVmlCanvas2D.prototype.closeOp = "x";

/**
 * Variable: rotatedHtmlBackground
 *
 * Background color for rotated HTML. Default is ''. This can be set to eg.
 * white to improve rendering of rotated text in VML for IE9.
 */
mxVmlCanvas2D.prototype.rotatedHtmlBackground = "";

/**
 * Variable: vmlScale
 *
 * Specifies the scale used to draw VML shapes.
 */
mxVmlCanvas2D.prototype.vmlScale = 1;

/**
 * Function: createElement
 *
 * Creates the given element using the document.
 */
mxVmlCanvas2D.prototype.createElement = function (name) {
  return document.createElement(name);
};

/**
 * Function: createVmlElement
 *
 * Creates a new element using <createElement> and prefixes the given name with
 * <mxClient.VML_PREFIX>.
 */
mxVmlCanvas2D.prototype.createVmlElement = function (name) {
  return this.createElement(mxClient.VML_PREFIX + ":" + name);
};

/**
 * Function: addNode
 *
 * Adds the current node to the <root>.
 */
mxVmlCanvas2D.prototype.addNode = function (filled, stroked) {
  var node = this.node;
  var s = this.state;

  if (node != null) {
    if (node.nodeName == "shape") {
      // Checks if the path is not empty
      if (this.path != null && this.path.length > 0) {
        node.path = this.path.join(" ") + " e";
        node.style.width = this.root.style.width;
        node.style.height = this.root.style.height;
        node.coordsize =
          parseInt(node.style.width) + " " + parseInt(node.style.height);
      } else {
        return;
      }
    }

    node.strokeweight =
      this.format(Math.max(1, (s.strokeWidth * s.scale) / this.vmlScale)) +
      "px";

    if (s.shadow) {
      this.root.appendChild(
        this.createShadow(
          node,
          filled && s.fillColor != null,
          stroked && s.strokeColor != null
        )
      );
    }

    if (stroked && s.strokeColor != null) {
      node.stroked = "true";
      node.strokecolor = s.strokeColor;
    } else {
      node.stroked = "false";
    }

    node.appendChild(this.createStroke());

    if (filled && s.fillColor != null) {
      node.appendChild(this.createFill());
    } else if (
      this.pointerEvents &&
      (node.nodeName != "shape" ||
        this.path[this.path.length - 1] == this.closeOp)
    ) {
      node.appendChild(this.createTransparentFill());
    } else {
      node.filled = "false";
    }

    // LATER: Update existing DOM for performance
    this.root.appendChild(node);
  }
};

/**
 * Function: createTransparentFill
 *
 * Creates a transparent fill.
 */
mxVmlCanvas2D.prototype.createTransparentFill = function () {
  var fill = this.createVmlElement("fill");
  fill.src = mxClient.imageBasePath + "/transparent.gif";
  fill.type = "tile";

  return fill;
};

/**
 * Function: createFill
 *
 * Creates a fill for the current state.
 */
mxVmlCanvas2D.prototype.createFill = function () {
  var s = this.state;

  // Gradients in foregrounds not supported because special gradients
  // with bounds must be created for each element in graphics-canvases
  var fill = this.createVmlElement("fill");
  fill.color = s.fillColor;

  if (s.gradientColor != null) {
    fill.type = "gradient";
    fill.method = "none";
    fill.color2 = s.gradientColor;
    var angle = 180 - s.rotation;

    if (s.gradientDirection == mxConstants.DIRECTION_WEST) {
      angle -= 90 + (this.root.style.flip == "x" ? 180 : 0);
    } else if (s.gradientDirection == mxConstants.DIRECTION_EAST) {
      angle += 90 + (this.root.style.flip == "x" ? 180 : 0);
    } else if (s.gradientDirection == mxConstants.DIRECTION_NORTH) {
      angle -= 180 + (this.root.style.flip == "y" ? -180 : 0);
    } else {
      angle += this.root.style.flip == "y" ? -180 : 0;
    }

    if (this.root.style.flip == "x" || this.root.style.flip == "y") {
      angle *= -1;
    }

    // LATER: Fix outer bounding box for rotated shapes used in VML.
    fill.angle = mxUtils.mod(angle, 360);
    fill.opacity = s.alpha * s.gradientFillAlpha * 100 + "%";
    fill.setAttribute(
      mxClient.OFFICE_PREFIX + ":opacity2",
      s.alpha * s.gradientAlpha * 100 + "%"
    );
  } else if (s.alpha < 1 || s.fillAlpha < 1) {
    fill.opacity = s.alpha * s.fillAlpha * 100 + "%";
  }

  return fill;
};
/**
 * Function: createStroke
 *
 * Creates a fill for the current state.
 */
mxVmlCanvas2D.prototype.createStroke = function () {
  var s = this.state;
  var stroke = this.createVmlElement("stroke");
  stroke.endcap = s.lineCap || "flat";
  stroke.joinstyle = s.lineJoin || "miter";
  stroke.miterlimit = s.miterLimit || "10";

  if (s.alpha < 1 || s.strokeAlpha < 1) {
    stroke.opacity = s.alpha * s.strokeAlpha * 100 + "%";
  }

  if (s.dashed) {
    stroke.dashstyle = this.getVmlDashStyle();
  }

  return stroke;
};

/**
 * Function: getVmlDashPattern
 *
 * Returns a VML dash pattern for the current dashPattern.
 * See http://msdn.microsoft.com/en-us/library/bb264085(v=vs.85).aspx
 */
mxVmlCanvas2D.prototype.getVmlDashStyle = function () {
  var result = "dash";

  if (typeof this.state.dashPattern === "string") {
    var tok = this.state.dashPattern.split(" ");

    if (tok.length > 0 && tok[0] == 1) {
      result = "0 2";
    }
  }

  return result;
};

/**
 * Function: createShadow
 *
 * Creates a shadow for the given node.
 */
mxVmlCanvas2D.prototype.createShadow = function (node, filled, stroked) {
  var s = this.state;
  var rad = -s.rotation * (Math.PI / 180);
  var cos = Math.cos(rad);
  var sin = Math.sin(rad);

  var dx = s.shadowDx * s.scale;
  var dy = s.shadowDy * s.scale;

  if (this.root.style.flip == "x") {
    dx *= -1;
  } else if (this.root.style.flip == "y") {
    dy *= -1;
  }

  var shadow = node.cloneNode(true);
  shadow.style.marginLeft = Math.round(dx * cos - dy * sin) + "px";
  shadow.style.marginTop = Math.round(dx * sin + dy * cos) + "px";

  // Workaround for wrong cloning in IE8 standards mode
  if (document.documentMode == 8) {
    shadow.strokeweight = node.strokeweight;

    if (node.nodeName == "shape") {
      shadow.path = this.path.join(" ") + " e";
      shadow.style.width = this.root.style.width;
      shadow.style.height = this.root.style.height;
      shadow.coordsize =
        parseInt(node.style.width) + " " + parseInt(node.style.height);
    }
  }

  if (stroked) {
    shadow.strokecolor = s.shadowColor;
    shadow.appendChild(this.createShadowStroke());
  } else {
    shadow.stroked = "false";
  }

  if (filled) {
    shadow.appendChild(this.createShadowFill());
  } else {
    shadow.filled = "false";
  }

  return shadow;
};

/**
 * Function: createShadowFill
 *
 * Creates the fill for the shadow.
 */
mxVmlCanvas2D.prototype.createShadowFill = function () {
  var fill = this.createVmlElement("fill");
  fill.color = this.state.shadowColor;
  fill.opacity = this.state.alpha * this.state.shadowAlpha * 100 + "%";

  return fill;
};

/**
 * Function: createShadowStroke
 *
 * Creates the stroke for the shadow.
 */
mxVmlCanvas2D.prototype.createShadowStroke = function () {
  var stroke = this.createStroke();
  stroke.opacity = this.state.alpha * this.state.shadowAlpha * 100 + "%";

  return stroke;
};

/**
 * Function: rotate
 *
 * Sets the rotation of the canvas. Note that rotation cannot be concatenated.
 */
mxVmlCanvas2D.prototype.rotate = function (theta, flipH, flipV, cx, cy) {
  if (flipH && flipV) {
    theta += 180;
  } else if (flipH) {
    this.root.style.flip = "x";
  } else if (flipV) {
    this.root.style.flip = "y";
  }

  if (flipH ? !flipV : flipV) {
    theta *= -1;
  }

  this.root.style.rotation = theta;
  this.state.rotation = this.state.rotation + theta;
  this.state.rotationCx = cx;
  this.state.rotationCy = cy;
};

/**
 * Function: begin
 *
 * Extends superclass to create path.
 */
mxVmlCanvas2D.prototype.begin = function () {
  mxAbstractCanvas2D.prototype.begin.apply(this, arguments);
  this.node = this.createVmlElement("shape");
  this.node.style.position = "absolute";
};

/**
 * Function: quadTo
 *
 * Replaces quadratic curve with bezier curve in VML.
 */
mxVmlCanvas2D.prototype.quadTo = function (x1, y1, x2, y2) {
  var s = this.state;

  var cpx0 = (this.lastX + s.dx) * s.scale;
  var cpy0 = (this.lastY + s.dy) * s.scale;
  var qpx1 = (x1 + s.dx) * s.scale;
  var qpy1 = (y1 + s.dy) * s.scale;
  var cpx3 = (x2 + s.dx) * s.scale;
  var cpy3 = (y2 + s.dy) * s.scale;

  var cpx1 = cpx0 + (2 / 3) * (qpx1 - cpx0);
  var cpy1 = cpy0 + (2 / 3) * (qpy1 - cpy0);

  var cpx2 = cpx3 + (2 / 3) * (qpx1 - cpx3);
  var cpy2 = cpy3 + (2 / 3) * (qpy1 - cpy3);

  this.path.push(
    "c " +
      this.format(cpx1) +
      " " +
      this.format(cpy1) +
      " " +
      this.format(cpx2) +
      " " +
      this.format(cpy2) +
      " " +
      this.format(cpx3) +
      " " +
      this.format(cpy3)
  );
  this.lastX = cpx3 / s.scale - s.dx;
  this.lastY = cpy3 / s.scale - s.dy;
};

/**
 * Function: createRect
 *
 * Sets the glass gradient.
 */
mxVmlCanvas2D.prototype.createRect = function (nodeName, x, y, w, h) {
  var s = this.state;
  var n = this.createVmlElement(nodeName);
  n.style.position = "absolute";
  n.style.left = this.format((x + s.dx) * s.scale) + "px";
  n.style.top = this.format((y + s.dy) * s.scale) + "px";
  n.style.width = this.format(w * s.scale) + "px";
  n.style.height = this.format(h * s.scale) + "px";

  return n;
};

/**
 * Function: rect
 *
 * Sets the current path to a rectangle.
 */
mxVmlCanvas2D.prototype.rect = function (x, y, w, h) {
  this.node = this.createRect("rect", x, y, w, h);
};

/**
 * Function: roundrect
 *
 * Sets the current path to a rounded rectangle.
 */
mxVmlCanvas2D.prototype.roundrect = function (x, y, w, h, dx, dy) {
  this.node = this.createRect("roundrect", x, y, w, h);
  // SetAttribute needed here for IE8
  this.node.setAttribute(
    "arcsize",
    Math.max((dx * 100) / w, (dy * 100) / h) + "%"
  );
};

/**
 * Function: ellipse
 *
 * Sets the current path to an ellipse.
 */
mxVmlCanvas2D.prototype.ellipse = function (x, y, w, h) {
  this.node = this.createRect("oval", x, y, w, h);
};

/**
 * Function: image
 *
 * Paints an image.
 */
mxVmlCanvas2D.prototype.image = function (
  x,
  y,
  w,
  h,
  src,
  aspect,
  flipH,
  flipV
) {
  var node = null;

  if (!aspect) {
    node = this.createRect("image", x, y, w, h);
    node.src = src;
  } else {
    // Uses fill with aspect to avoid asynchronous update of size
    node = this.createRect("rect", x, y, w, h);
    node.stroked = "false";

    // Handles image aspect via fill
    var fill = this.createVmlElement("fill");
    fill.aspect = aspect ? "atmost" : "ignore";
    fill.rotate = "true";
    fill.type = "frame";
    fill.src = src;

    node.appendChild(fill);
  }

  if (flipH && flipV) {
    node.style.rotation = "180";
  } else if (flipH) {
    node.style.flip = "x";
  } else if (flipV) {
    node.style.flip = "y";
  }

  if (this.state.alpha < 1 || this.state.fillAlpha < 1) {
    // KNOWN: Borders around transparent images in IE<9. Using fill.opacity
    // fixes this problem by adding a white background in all IE versions.
    node.style.filter +=
      "alpha(opacity=" + this.state.alpha * this.state.fillAlpha * 100 + ")";
  }

  this.root.appendChild(node);
};

/**
 * Function: createText
 *
 * Creates the innermost element that contains the HTML text.
 */
mxVmlCanvas2D.prototype.createDiv = function (str, align, valign, overflow) {
  var div = this.createElement("div");
  var state = this.state;

  var css = "";

  if (state.fontBackgroundColor != null) {
    css +=
      "background-color:" +
      mxUtils.htmlEntities(state.fontBackgroundColor) +
      ";";
  }

  if (state.fontBorderColor != null) {
    css +=
      "border:1px solid " + mxUtils.htmlEntities(state.fontBorderColor) + ";";
  }

  if (mxUtils.isNode(str)) {
    div.appendChild(str);
  } else {
    if (overflow != "fill" && overflow != "width") {
      var div2 = this.createElement("div");
      div2.style.cssText = css;
      div2.style.display = mxClient.IS_QUIRKS ? "inline" : "inline-block";
      div2.style.zoom = "1";
      div2.style.textDecoration = "inherit";
      div2.innerHTML = str;
      div.appendChild(div2);
    } else {
      div.style.cssText = css;
      div.innerHTML = str;
    }
  }

  var style = div.style;

  style.fontSize = state.fontSize / this.vmlScale + "px";
  style.fontFamily = state.fontFamily;
  style.color = state.fontColor;
  style.verticalAlign = "top";
  style.textAlign = align || "left";
  style.lineHeight = mxConstants.ABSOLUTE_LINE_HEIGHT
    ? (state.fontSize * mxConstants.LINE_HEIGHT) / this.vmlScale + "px"
    : mxConstants.LINE_HEIGHT;

  if ((state.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) {
    style.fontWeight = "bold";
  }

  if ((state.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) {
    style.fontStyle = "italic";
  }

  if (
    (state.fontStyle & mxConstants.FONT_UNDERLINE) ==
    mxConstants.FONT_UNDERLINE
  ) {
    style.textDecoration = "underline";
  }

  return div;
};

/**
 * Function: text
 *
 * Paints the given text. Possible values for format are empty string for plain
 * text and html for HTML markup. Clipping, text background and border are not
 * supported for plain text in VML.
 */
mxVmlCanvas2D.prototype.text = function (
  x,
  y,
  w,
  h,
  str,
  align,
  valign,
  wrap,
  format,
  overflow,
  clip,
  rotation,
  dir
) {
  if (this.textEnabled && str != null) {
    var s = this.state;

    if (format == "html") {
      if (s.rotation != null) {
        var pt = this.rotatePoint(x, y, s.rotation, s.rotationCx, s.rotationCy);

        x = pt.x;
        y = pt.y;
      }

      if (document.documentMode == 8 && !mxClient.IS_EM) {
        x += s.dx;
        y += s.dy;

        // Workaround for rendering offsets
        if (overflow != "fill" && valign == mxConstants.ALIGN_TOP) {
          y -= 1;
        }
      } else {
        x *= s.scale;
        y *= s.scale;
      }

      // Adds event transparency in IE8 standards without the transparent background
      // filter which cannot be used due to bugs in the zoomed bounding box (too slow)
      // FIXME: No event transparency if inside v:rect (ie part of shape)
      // KNOWN: Offset wrong for rotated text with word that are longer than the wrapping
      // width in IE8 because real width of text cannot be determined here.
      // This should be fixed in mxText.updateBoundingBox by calling before this and
      // passing the real width to this method if not clipped and wrapped.
      var abs =
        document.documentMode == 8 && !mxClient.IS_EM
          ? this.createVmlElement("group")
          : this.createElement("div");
      abs.style.position = "absolute";
      abs.style.display = "inline";
      abs.style.left = this.format(x) + "px";
      abs.style.top = this.format(y) + "px";
      abs.style.zoom = s.scale;

      var box = this.createElement("div");
      box.style.position = "relative";
      box.style.display = "inline";

      var margin = mxUtils.getAlignmentAsPoint(align, valign);
      var dx = margin.x;
      var dy = margin.y;

      var div = this.createDiv(str, align, valign, overflow);
      var inner = this.createElement("div");

      if (dir != null) {
        div.setAttribute("dir", dir);
      }

      if (wrap && w > 0) {
        if (!clip) {
          div.style.width = Math.round(w) + "px";
        }

        div.style.wordWrap = mxConstants.WORD_WRAP;
        div.style.whiteSpace = "normal";

        // LATER: Check if other cases need to be handled
        if (div.style.wordWrap == "break-word") {
          var tmp = div;

          if (tmp.firstChild != null && tmp.firstChild.nodeName == "DIV") {
            tmp.firstChild.style.width = "100%";
          }
        }
      } else {
        div.style.whiteSpace = "nowrap";
      }

      var rot = s.rotation + (rotation || 0);

      if (this.rotateHtml && rot != 0) {
        inner.style.display = "inline";
        inner.style.zoom = "1";
        inner.appendChild(div);

        // Box not needed for rendering in IE8 standards
        if (
          document.documentMode == 8 &&
          !mxClient.IS_EM &&
          this.root.nodeName != "DIV"
        ) {
          box.appendChild(inner);
          abs.appendChild(box);
        } else {
          abs.appendChild(inner);
        }
      } else if (document.documentMode == 8 && !mxClient.IS_EM) {
        box.appendChild(div);
        abs.appendChild(box);
      } else {
        div.style.display = "inline";
        abs.appendChild(div);
      }

      // Inserts the node into the DOM
      if (this.root.nodeName != "DIV") {
        // Rectangle to fix position in group
        var rect = this.createVmlElement("rect");
        rect.stroked = "false";
        rect.filled = "false";

        rect.appendChild(abs);
        this.root.appendChild(rect);
      } else {
        this.root.appendChild(abs);
      }

      if (clip) {
        div.style.overflow = "hidden";
        div.style.width = Math.round(w) + "px";

        if (!mxClient.IS_QUIRKS) {
          div.style.maxHeight = Math.round(h) + "px";
        }
      } else if (overflow == "fill") {
        // KNOWN: Affects horizontal alignment in quirks
        // but fill should only be used with align=left
        div.style.overflow = "hidden";
        div.style.width = Math.max(0, w) + 1 + "px";
        div.style.height = Math.max(0, h) + 1 + "px";
      } else if (overflow == "width") {
        // KNOWN: Affects horizontal alignment in quirks
        // but fill should only be used with align=left
        div.style.overflow = "hidden";
        div.style.width = Math.max(0, w) + 1 + "px";
        div.style.maxHeight = Math.max(0, h) + 1 + "px";
      }

      if (this.rotateHtml && rot != 0) {
        var rad = rot * (Math.PI / 180);

        // Precalculate cos and sin for the rotation
        var real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8));
        var real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8));

        rad %= 2 * Math.PI;
        if (rad < 0) rad += 2 * Math.PI;
        rad %= Math.PI;
        if (rad > Math.PI / 2) rad = Math.PI - rad;

        var cos = Math.cos(rad);
        var sin = Math.sin(rad);

        // Adds div to document to measure size
        if (document.documentMode == 8 && !mxClient.IS_EM) {
          div.style.display = "inline-block";
          inner.style.display = "inline-block";
          box.style.display = "inline-block";
        }

        div.style.visibility = "hidden";
        div.style.position = "absolute";
        document.body.appendChild(div);

        var sizeDiv = div;

        if (
          sizeDiv.firstChild != null &&
          sizeDiv.firstChild.nodeName == "DIV"
        ) {
          sizeDiv = sizeDiv.firstChild;
        }

        var tmp = sizeDiv.offsetWidth + 3;
        var oh = sizeDiv.offsetHeight;

        if (clip) {
          w = Math.min(w, tmp);
          oh = Math.min(oh, h);
        } else {
          w = tmp;
        }

        // Handles words that are longer than the given wrapping width
        if (wrap) {
          div.style.width = w + "px";
        }

        // Simulates max-height in quirks
        if (mxClient.IS_QUIRKS && (clip || overflow == "width") && oh > h) {
          oh = h;

          // Quirks does not support maxHeight
          div.style.height = oh + "px";
        }

        h = oh;

        var top_fix =
          (h - h * cos + w * -sin) / 2 -
          real_sin * w * (dx + 0.5) +
          real_cos * h * (dy + 0.5);
        var left_fix =
          (w - w * cos + h * -sin) / 2 +
          real_cos * w * (dx + 0.5) +
          real_sin * h * (dy + 0.5);

        if (abs.nodeName == "group" && this.root.nodeName == "DIV") {
          // Workaround for bug where group gets moved away if left and top are non-zero in IE8 standards
          var pos = this.createElement("div");
          pos.style.display = "inline-block";
          pos.style.position = "absolute";
          pos.style.left = this.format(x + (left_fix - w / 2) * s.scale) + "px";
          pos.style.top = this.format(y + (top_fix - h / 2) * s.scale) + "px";

          abs.parentNode.appendChild(pos);
          pos.appendChild(abs);
        } else {
          var sc = document.documentMode == 8 && !mxClient.IS_EM ? 1 : s.scale;

          abs.style.left = this.format(x + (left_fix - w / 2) * sc) + "px";
          abs.style.top = this.format(y + (top_fix - h / 2) * sc) + "px";
        }

        // KNOWN: Rotated text rendering quality is bad for IE9 quirks
        inner.style.filter =
          "progid:DXImageTransform.Microsoft.Matrix(M11=" +
          real_cos +
          ", M12=" +
          real_sin +
          ", M21=" +
          -real_sin +
          ", M22=" +
          real_cos +
          ", sizingMethod='auto expand')";
        inner.style.backgroundColor = this.rotatedHtmlBackground;

        if (this.state.alpha < 1) {
          inner.style.filter += "alpha(opacity=" + this.state.alpha * 100 + ")";
        }

        // Restore parent node for DIV
        inner.appendChild(div);
        div.style.position = "";
        div.style.visibility = "";
      } else if (document.documentMode != 8 || mxClient.IS_EM) {
        div.style.verticalAlign = "top";

        if (this.state.alpha < 1) {
          abs.style.filter = "alpha(opacity=" + this.state.alpha * 100 + ")";
        }

        // Adds div to document to measure size
        var divParent = div.parentNode;
        div.style.visibility = "hidden";
        document.body.appendChild(div);

        w = div.offsetWidth;
        var oh = div.offsetHeight;

        // Simulates max-height in quirks
        if (mxClient.IS_QUIRKS && clip && oh > h) {
          oh = h;

          // Quirks does not support maxHeight
          div.style.height = oh + "px";
        }

        h = oh;

        div.style.visibility = "";
        divParent.appendChild(div);

        abs.style.left = this.format(x + w * dx * this.state.scale) + "px";
        abs.style.top = this.format(y + h * dy * this.state.scale) + "px";
      } else {
        if (this.state.alpha < 1) {
          div.style.filter = "alpha(opacity=" + this.state.alpha * 100 + ")";
        }

        // Faster rendering in IE8 without offsetWidth/Height
        box.style.left = dx * 100 + "%";
        box.style.top = dy * 100 + "%";
      }
    } else {
      this.plainText(
        x,
        y,
        w,
        h,
        mxUtils.htmlEntities(str, false),
        align,
        valign,
        wrap,
        format,
        overflow,
        clip,
        rotation,
        dir
      );
    }
  }
};

/**
 * Function: plainText
 *
 * Paints the outline of the current path.
 */
mxVmlCanvas2D.prototype.plainText = function (
  x,
  y,
  w,
  h,
  str,
  align,
  valign,
  wrap,
  format,
  overflow,
  clip,
  rotation,
  dir
) {
  // TextDirection is ignored since this code is not used (format is always HTML in the text function)
  var s = this.state;
  x = (x + s.dx) * s.scale;
  y = (y + s.dy) * s.scale;

  var node = this.createVmlElement("shape");
  node.style.width = "1px";
  node.style.height = "1px";
  node.stroked = "false";

  var fill = this.createVmlElement("fill");
  fill.color = s.fontColor;
  fill.opacity = s.alpha * 100 + "%";
  node.appendChild(fill);

  var path = this.createVmlElement("path");
  path.textpathok = "true";
  path.v =
    "m " +
    this.format(0) +
    " " +
    this.format(0) +
    " l " +
    this.format(1) +
    " " +
    this.format(0);

  node.appendChild(path);

  // KNOWN: Font family and text decoration ignored
  var tp = this.createVmlElement("textpath");
  tp.style.cssText = "v-text-align:" + align;
  tp.style.align = align;
  tp.style.fontFamily = s.fontFamily;
  tp.string = str;
  tp.on = "true";

  // Scale via fontsize instead of node.style.zoom for correct offsets in IE8
  var size = (s.fontSize * s.scale) / this.vmlScale;
  tp.style.fontSize = size + "px";

  // Bold
  if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) {
    tp.style.fontWeight = "bold";
  }

  // Italic
  if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) {
    tp.style.fontStyle = "italic";
  }

  // Underline
  if (
    (s.fontStyle & mxConstants.FONT_UNDERLINE) ==
    mxConstants.FONT_UNDERLINE
  ) {
    tp.style.textDecoration = "underline";
  }

  var lines = str.split("\n");
  var textHeight = size + (lines.length - 1) * size * mxConstants.LINE_HEIGHT;
  var dx = 0;
  var dy = 0;

  if (valign == mxConstants.ALIGN_BOTTOM) {
    dy = -textHeight / 2;
  } else if (valign != mxConstants.ALIGN_MIDDLE) {
    // top
    dy = textHeight / 2;
  }

  if (rotation != null) {
    node.style.rotation = rotation;
    var rad = rotation * (Math.PI / 180);
    dx = Math.sin(rad) * dy;
    dy = Math.cos(rad) * dy;
  }

  // FIXME: Clipping is relative to bounding box
  /*if (clip)
	{
		node.style.clip = 'rect(0px ' + this.format(w) + 'px ' + this.format(h) + 'px 0px)';
	}*/

  node.appendChild(tp);
  node.style.left = this.format(x - dx) + "px";
  node.style.top = this.format(y + dy) + "px";

  this.root.appendChild(node);
};

/**
 * Function: stroke
 *
 * Paints the outline of the current path.
 */
mxVmlCanvas2D.prototype.stroke = function () {
  this.addNode(false, true);
};

/**
 * Function: fill
 *
 * Fills the current path.
 */
mxVmlCanvas2D.prototype.fill = function () {
  this.addNode(true, false);
};

/**
 * Function: fillAndStroke
 *
 * Fills and paints the outline of the current path.
 */
mxVmlCanvas2D.prototype.fillAndStroke = function () {
  this.addNode(true, true);
};
